home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 …ember: Reference Library / Dev.CD Dec 96 RL / Dev.CD Dec 96 RL.toast / Technical Documentation / develop / develop Issue 23 / develop Issue 23 code / ProjectDrag 1.1b8.sea / ProjectDrag 1.1b8 / Sources / ProjectDrag Sources / Comments.c / Comments.c
Encoding:
C/C++ Source or Header  |  1995-09-07  |  29.8 KB  |  1,139 lines  |  [TEXT/MPS ]

  1. /* Comments.c: Comment handling for ProjectDrag
  2.  *
  3.  * A set of applets for drag and drop source control by Tim Maroney.
  4.  * See develop, issue 23 for details.
  5.  *
  6.  * Built on DropShell by Leonard Rosenthol, Stephan Somogyi, and Marshall Clow,
  7.  * and using the MoreFiles utilities by Jim Luther.
  8.  *
  9.  * This software is free, but don't modify and redistribute it without
  10.  * changing the status window to indicate your name and your changes!
  11.  */
  12.  
  13.  
  14. #include <Types.h>
  15. #include <Resources.h>
  16. #include <Dialogs.h>
  17. #include <ToolUtils.h>
  18.  
  19. #include "PDUtilities.h"
  20. #include "PDDialogs.h"
  21. #include "Comments.h"
  22. #include "TasksAndErrors.h"
  23.  
  24.  
  25. #define kChangeCommentDialog 204
  26. #define kCommentItem 5
  27.  
  28.  
  29. typedef struct
  30. {
  31.     StringPtr extension;
  32.     StringPtr commentStart;
  33.     StringPtr lineStart;
  34.     StringPtr lineEnd;
  35.     StringPtr blankLine;
  36.     StringPtr commentEnd;
  37. } CommentFormat;
  38.  
  39.  
  40. /* XXX yeah, yeah, this table should be stored in a user-editable file -- maybe in 1.2 */
  41.  
  42. #define kNumCommentFormats 12
  43.  
  44. CommentFormat pCommentFormats[kNumCommentFormats] =
  45. {
  46.     { "\p.c", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  47.     { "\p.h", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  48.     { "\p.cp", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  49.     { "\p.cc", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  50.     { "\p.cpp", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  51.     { "\p.pch", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  52.     { "\p.idl", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  53.     { "\p.r", "\p/*\n", "\p", "\p", "\p", "\p*/\n" },
  54.     { "\p.p", "\p(*\n", "\p", "\p", "\p", "\p*)\n" },
  55.     { "\p.a", "\p", "\p*", "\p", "\p*", "\p*\n" },
  56.     { "\p.s", "\p", "\p*", "\p", "\p*", "\p*\n" },
  57.     { "\p", "\p#\n", "\p#", "\p", "\p#", "\p#\n" } /* this line must be last */
  58. };
  59.  
  60.  
  61. pascal Boolean TheFilterProc(DialogPtr theDialog,EventRecord *ev,short *itemHit)
  62. {
  63.     return StdFilterProc(theDialog, ev, itemHit);
  64. }
  65.  
  66.  
  67. void SavePreviousComment(StringPtr comment)
  68. {
  69.     OSErr err;
  70.     FSSpec file;
  71.     Handle fileData = NULL;
  72.     short refNum = -1;
  73.     
  74.     /* find the preferences folder */
  75.     err = FindPreferencesFolder(&file.vRefNum, &file.parID);
  76.     if (err != noErr) goto ErrorExit;
  77.     
  78.     /* set the Previous Comment file data */
  79.     GetIndString(file.name, kProjectDragStrings, kPreviousCommentFileName);
  80.     err = GetFileData(&file, &fileData, &refNum);
  81.     if (err == fnfErr)
  82.     {
  83.         /* create and open the file */
  84.         err = FSpCreate(&file, 'MPS ', 'TEXT', smSystemScript);
  85.         if (err != noErr) goto ErrorExit;
  86.         err = FSpOpenDF(&file, fsRdWrPerm, &refNum);
  87.         if (err != noErr) goto ErrorExit;
  88.         fileData = TempNewHandle(0, &err);
  89.     }
  90.     if (err != noErr) goto ErrorExit;
  91.     SetHandleSize(fileData, 0);
  92.     err = PtrAndHand(comment + 1, fileData, comment[0]);
  93.     if (err != noErr) goto ErrorExit;
  94.     err = WriteFileWithHeader(refNum, fileData, 0, NULL);
  95.     if (err != noErr) goto ErrorExit;
  96.     
  97.     /* clean up and go */
  98.     DisposeHandle(fileData);
  99.     FSClose(refNum);
  100.     return;
  101.  
  102. ErrorExit:
  103.     if (fileData != NULL)
  104.         DisposeHandle(fileData);
  105.     if (refNum != -1)
  106.         FSClose(refNum);
  107. }
  108.  
  109.  
  110. void FetchPreviousComment(StringPtr comment)
  111. {
  112.     OSErr err;
  113.     FSSpec file;
  114.     Handle fileData;
  115.     short refNum;
  116.     
  117.     comment[0] = 0;
  118.     
  119.     /* find the preferences folder */
  120.     err = FindPreferencesFolder(&file.vRefNum, &file.parID);
  121.     if (err != noErr) return;
  122.     
  123.     /* get the Previous Comment file data */
  124.     GetIndString(file.name, kProjectDragStrings, kPreviousCommentFileName);
  125.     err = GetFileData(&file, &fileData, &refNum);
  126.     if (err != noErr) return;
  127.     FSClose(refNum);
  128.     comment[0] = GetHandleSize(fileData);
  129.     BlockMoveData(*fileData, comment + 1, comment[0]);
  130.     DisposeHandle(fileData);
  131. }
  132.  
  133.  
  134. Boolean GetChangeComment(Boolean in, ConstStr255Param fileName, Str255 comment)
  135. {
  136.     DialogPtr dialog;
  137.     Boolean done = false;
  138.     Boolean result = false;
  139.     OSErr err = noErr;
  140.     Str255 what;
  141.     Str255 s;
  142.     Rect r;
  143.     Handle h;
  144.     short type;
  145.     
  146.     GetIndString(s, kProjectDragStrings, kExplainChange);
  147.     if (in)
  148.         GetIndString(what, kProjectDragStrings, kWhatYouChangedIn);
  149.     else
  150.         GetIndString(what, kProjectDragStrings, kWhatYouWillDoWith);
  151.     err = PtrToHand(s + 1, &h, s[0]);
  152.     err = ReplaceString(h, "\p<1>", what);
  153.     err = ReplaceString(h, "\p<2>", fileName);
  154.     s[0] = GetHandleSize(h);
  155.     BlockMoveData(*h, s + 1, s[0]);
  156.     DisposeHandle(h);
  157.     ParamText(s, NULL, NULL, NULL);
  158.     
  159.     dialog = GetNewDialog(kChangeCommentDialog, NULL, (WindowPtr)-1);
  160.     if (dialog == NULL) return false;
  161.     if (comment[0] == 0) FetchPreviousComment(comment);
  162.     GetDialogItem(dialog, kCommentItem, &type, &h, &r);
  163.     SetDialogItemText(h, comment);
  164.     SelectDialogItemText(dialog, kCommentItem, 0, 32767);
  165.     SetDialogDefaultItem(dialog, ok);
  166.     SetDialogCancelItem(dialog, cancel);
  167.     SetDialogTracksCursor(dialog, true);
  168.     ShowWindow(dialog);
  169.     while (!done)
  170.     {
  171.         short itemHit;
  172.         
  173.         ModalDialog(TheFilterProc, &itemHit);
  174.         switch (itemHit)
  175.         {
  176.         case ok:
  177.             GetDialogItem(dialog, kCommentItem, &type, &h, &r);
  178.             GetDialogItemText(h, s);
  179.             BlockMove(s, comment, s[0] + 1);
  180.             SavePreviousComment(comment);
  181.             done = true;
  182.             result = true;
  183.             break;
  184.             
  185.         case cancel:
  186.             err = userCanceledErr;
  187.             done = true;
  188.             break;
  189.         }
  190.     }
  191.     DisposeDialog(dialog);
  192.     return result;
  193. }
  194.  
  195.  
  196. Boolean IsTextFile(FSSpec *file)
  197. {
  198.     FInfo info;
  199.     OSErr err;
  200.     
  201.     err = FSpGetFInfo(file, &info);
  202.     if (err != noErr) return false;
  203.     return info.fdType == 'TEXT';
  204. }
  205.  
  206.  
  207. CommentFormat *GetCommentFormat(FSSpec *file)
  208. {
  209.     Str15 extension;
  210.     long i, length;
  211.     
  212.     /* get the extension from the file name */
  213.     for (i = file->name[0]; i > 0 && file->name[i] != '.'; i--)
  214.         ;
  215.     length = (file->name[0] - i) + 1;
  216.     if (length > 5)
  217.         return pCommentFormats + (kNumCommentFormats - 1);
  218.     BlockMoveData(file->name + i, extension + 1, length);
  219.     extension[0] = length;
  220.     
  221.     /* loop over the formats */
  222.     for (i = 0; i < kNumCommentFormats; i++)
  223.     {
  224.         if (EqualString(extension, pCommentFormats[i].extension, false, true))
  225.             return pCommentFormats + i;
  226.     }
  227.     
  228.     /* return the default */
  229.     return pCommentFormats + (kNumCommentFormats - 1);
  230. }
  231.  
  232.  
  233. OSErr SubstituteComments(StringHandle header, CommentFormat *format)
  234. {
  235.     OSErr err;
  236.     err = ReplaceString(header, "\p<comment start>", format->commentStart);
  237.     if (err < noErr) return err;
  238.     err = ReplaceString(header, "\p<line start>", format->lineStart);
  239.     if (err < noErr) return err;
  240.     err = ReplaceString(header, "\p<line end>", format->lineEnd);
  241.     if (err < noErr) return err;
  242.     err = ReplaceString(header, "\p<blank line>", format->blankLine);
  243.     if (err < noErr) return err;
  244.     if (err > noErr) err = noErr;
  245.     return err;
  246. }
  247.  
  248.  
  249.  
  250. OSErr GetHeaderFromFile(Handle fileData, CommentFormat *format,
  251.                         StringHandle blankHeader, StringHandle *header)
  252. {
  253.     OSErr err;
  254.     Byte headerState, fileState;
  255.     StringPtr fileLine;
  256.     StringPtr headerLine;
  257.     Boolean lineDoesntMatch;
  258.     Boolean neverMatched;
  259.     Boolean lastLineMatchedCommentEnd;
  260.     long headerLineLength, headerLength, fileLineLength, fileLength;
  261.     
  262.     /* loop over lines to match each line of blank header,
  263.      * accumulating lines into the header string handle
  264.      */
  265.     *header = NULL;
  266.     headerState = HGetState(blankHeader);
  267.     HLock(blankHeader);
  268.     fileState = HGetState(fileData);
  269.     HLock(fileData);
  270.     fileLength = GetHandleSize(fileData);
  271.     headerLength = GetHandleSize(blankHeader);
  272.     headerLine = *blankHeader;
  273.     fileLine = *fileData;
  274.     headerLineLength = LineSize(headerLine, headerLength);
  275.     fileLineLength = LineSize(fileLine, fileLength);
  276.     lineDoesntMatch = true;
  277.     neverMatched = true;
  278.     lastLineMatchedCommentEnd = false;
  279.     do
  280.     {
  281.         /* check for end of comment -- it means failure when followed by a blank line */
  282.         if (lastLineMatchedCommentEnd && fileLineLength == 1 && *fileLine == '\n')
  283.         {
  284.             err = -1;
  285.             break;
  286.         }
  287.         
  288.         lastLineMatchedCommentEnd = MatchLine(fileLine, fileLineLength, format->commentEnd + 1, format->commentEnd[0]);
  289.  
  290.         if (MatchLineUntilChar(fileLine, fileLineLength, headerLine, headerLineLength, '<'))
  291.         {
  292.             lineDoesntMatch = neverMatched = false;
  293.             
  294.             /* start matching file to next header line */
  295.             headerLine += headerLineLength;
  296.             headerLength -= headerLineLength;
  297.             headerLineLength = LineSize(headerLine, headerLength);
  298.             
  299.             /* skip blank lines in header */
  300.             while (headerLength > 0
  301.                    && MatchLine(headerLine, headerLineLength, format->lineStart + 1,
  302.                                    format->lineStart[0]))
  303.             {
  304.                 headerLine += headerLineLength;
  305.                 headerLength -= headerLineLength;
  306.                 headerLineLength = LineSize(headerLine, headerLength);
  307.             }
  308.         }
  309.         else
  310.         {
  311.             lineDoesntMatch = true;
  312.             
  313.             /* it's OK if a line doesn't match as long as we have been matching
  314.              * up to this point, because extra lines of comment are OK.
  315.              */
  316.             if (neverMatched) break;
  317.         }
  318.         
  319.         /* accumulate line into header handle */
  320.         if (*header == NULL)
  321.         {
  322.             *header = TempNewHandle(0, &err);
  323.             if (err != noErr) break;
  324.         }
  325.         err = PtrAndHand(fileLine, *header, fileLineLength);
  326.         
  327.         /* advance the file data pointer */
  328.         fileLine += fileLineLength;
  329.         fileLength -= fileLineLength;
  330.         fileLineLength = LineSize(fileLine, fileLength);
  331.  
  332.     } while (err == noErr && headerLength > 0 && fileLength > 0);
  333.     
  334.     HSetState(fileData, fileState);
  335.     HSetState(blankHeader, headerState);
  336.     
  337.     if (lineDoesntMatch || err != noErr)
  338.     {
  339.         /* XXX put up a "header corrupt" warning when something matched */
  340.         if (*header != NULL)
  341.         {
  342.             DisposeHandle(*header);
  343.             *header = NULL;
  344.         }
  345.     }
  346.     
  347.     return (lineDoesntMatch && err == noErr) ? -1 : err;
  348. }
  349.  
  350.  
  351. OSErr PadToColumn(Handle h, long column)
  352. {
  353.     OSErr err;
  354.     long lineStart, i;
  355.     long sizeNow = GetHandleSize(h), newSize;
  356.     
  357.     /* track backwards looking for end of line */
  358.     for (lineStart = sizeNow; lineStart > 0 && (*h)[lineStart-1] != '\n'; lineStart--)
  359.         ;
  360.     
  361.     if (sizeNow - lineStart >= column)
  362.     {
  363.         /* already gone too far -- insert a single space */
  364.         SetHandleSize(h, sizeNow + 1);
  365.         err = MemError();
  366.         if (err != noErr) return err;
  367.         (*h)[sizeNow] = ' ';
  368.         return noErr;
  369.     }
  370.     
  371.     /* add spaces until we get to the column */
  372.     newSize = sizeNow + (column - (sizeNow - lineStart));
  373.     SetHandleSize(h, newSize);
  374.     err = MemError();
  375.     if (err != noErr) return err;
  376.     for (i = sizeNow; i < newSize; i++)
  377.         (*h)[i] = ' ';
  378.     return noErr;
  379. }
  380.  
  381.  
  382. Boolean IsChangeCommentStartLine(StringPtr headerLine, long length,
  383.                                  CommentFormat *format)
  384. {
  385.     StringPtr s = headerLine;
  386.     
  387.     /* match the start of line sequence, if not null */
  388.     if (format->lineStart != NULL && *format->lineStart != 0)
  389.     {
  390.         long len2 = format->lineStart[0];
  391.         StringPtr s2 = format->lineStart + 1;
  392.         while (len2-- > 0 && length-- > 0)
  393.         {
  394.             if (*s2++ != *s++)
  395.                 return false;
  396.         }
  397.     }
  398.     
  399.     /* skip any number of tabs and spaces */
  400.     while (*s == '\t' || *s == ' ') s++;
  401.  
  402.     /* found it if the first non-tab is an angle bracket */
  403.     return (*s == '<');
  404. }
  405.  
  406.  
  407. OSErr FindFirstChangeComment(StringHandle header, CommentFormat *format,
  408.                              long *start, long *end)
  409. {
  410.     StringPtr headerLine;
  411.     Byte state;
  412.     long headerLineLength;
  413.     long headerLength;
  414.     Boolean found = false;
  415.     Boolean lastLineMatchedCommentEnd;
  416.     
  417.     state = HGetState(header);
  418.     HLock(header);
  419.     headerLine = *header;
  420.     headerLength = GetHandleSize(header);
  421.     headerLineLength = LineSize(headerLine, headerLength);
  422.     lastLineMatchedCommentEnd = false;
  423.     
  424.     /* loop over lines to find first line starting with tabs and angle bracket */
  425.     do
  426.     {
  427.         /* check for end of comment */
  428.         if (lastLineMatchedCommentEnd && headerLineLength == 1 && *headerLine == '\n')
  429.         {
  430.             /* no change comment */
  431.             *start = *end = (headerLine - (*header)) - 1;
  432.             break;
  433.         }
  434.         
  435.         lastLineMatchedCommentEnd = MatchLine(headerLine, headerLineLength, format->commentEnd + 1, format->commentEnd[0]);
  436.         
  437.         found = IsChangeCommentStartLine(headerLine, headerLineLength, format);
  438.         
  439.         if (!found)
  440.         {
  441.             /* if not found, advance to next line */
  442.             headerLine += headerLineLength;
  443.             headerLength -= headerLineLength;
  444.             headerLineLength = LineSize(headerLine, headerLength);
  445.         }
  446.         else
  447.         {
  448.             /* if found, record the start of the comment and scan for the next or the end */
  449.             *start = headerLine - *header;
  450.             
  451.             headerLine += headerLineLength;
  452.             headerLength -= headerLineLength;
  453.             headerLineLength = LineSize(headerLine, headerLength);
  454.             lastLineMatchedCommentEnd = false;
  455.             do
  456.             {
  457.                 if (MatchLine(headerLine, headerLineLength, format->commentEnd + 1, format->commentEnd[0]))
  458.                     break; /* adding the first change comment */
  459.                 
  460.                 if (IsChangeCommentStartLine(headerLine, headerLineLength, format))
  461.                     break; /* found start of next change comment */
  462.                 
  463.                 /* try the next line */
  464.                 headerLine += headerLineLength;
  465.                 headerLength -= headerLineLength;
  466.                 headerLineLength = LineSize(headerLine, headerLength);
  467.             } while (headerLength > 0);
  468.             
  469.             *end = (headerLine - (*header)) - 1;
  470.         }
  471.     } while (headerLength > 0 && !found);
  472.     
  473.     HSetState(header, state);
  474.     
  475.     return noErr;
  476. }
  477.  
  478.  
  479. OSErr GetRevisionNumber(FSSpec *file, short *revision)
  480. {
  481.     CKIDHandle theCKID;
  482.     OSErr err;
  483.     
  484.     /* get the CKID */
  485.     err = ExtractCKID(file, &theCKID);
  486.     
  487.     if (err != noErr)
  488.     {
  489.         /* if no CKID, it's revision 0! */
  490.         *revision = 0;
  491.     }
  492.     else
  493.     {
  494.         /* otherwise it's the number in the CKID */
  495.         Str31 revisionString;
  496.         long longRevision;
  497.         StringPtr s = (*theCKID)->projectPath; /* careful -- not locked down */
  498.         s += s[0] + 2;        /* skip the project name and null character */
  499.         s += s[0] + 2;        /* skip the user name and null character */
  500.         BlockMoveData(s, revisionString, s[0] + 1); /* copy the revision number */
  501.         StringToNum(revisionString, &longRevision);
  502.         *revision = (short)longRevision;
  503.         DisposeHandle((Handle)theCKID);
  504.     }
  505.     return noErr;
  506. }
  507.  
  508.  
  509. OSErr BuildComment(Handle *commentHandle, CommentFormat *format, short revision,
  510.                     Boolean checkingOut, StringPtr comment, StringPtr nickname)
  511. {
  512.     OSErr err;
  513.     Str31 revisionString;
  514.     unsigned long sects;
  515.     Str31 dateString;
  516.     char c;
  517.     
  518.     /* Build the change comment, aligned to four-column format.
  519.      * The end of the revision number (">") falls on column twelve (12);
  520.      * the end of the date on column twenty-four (24);
  521.      * the start of the initials on column twenty-nine (29);
  522.      * the start of the comment on column thirty-seven (37).
  523.      */
  524.     err = PtrToHand(format->lineStart + 1, commentHandle, format->lineStart[0]);
  525.     if (err != noErr) goto ErrorExit;
  526.     
  527.     /* add the revision string */
  528.     NumToString(checkingOut ? revision : revision + 1, revisionString);
  529.     if (checkingOut) revisionString[++revisionString[0]] = '+';
  530.     err = PadToColumn(*commentHandle, 12 - (revisionString[0] + 2));
  531.     if (err != noErr) goto ErrorExit;
  532.     c = '<';
  533.     err = PtrAndHand(&c, *commentHandle, 1);
  534.     if (err != noErr) goto ErrorExit;
  535.     err = PtrAndHand(revisionString + 1, *commentHandle, revisionString[0]);
  536.     if (err != noErr) goto ErrorExit;
  537.     c = '>';
  538.     err = PtrAndHand(&c, *commentHandle, 1);
  539.     if (err != noErr) goto ErrorExit;
  540.     
  541.     /* add the date string */
  542.     GetDateTime(§s);
  543.     DateString(sects, shortDate, dateString, NULL);
  544.     err = PadToColumn(*commentHandle, 24 - dateString[0]);
  545.     if (err != noErr) goto ErrorExit;
  546.     err = PtrAndHand(dateString + 1, *commentHandle, dateString[0]);
  547.     if (err != noErr) goto ErrorExit;
  548.     
  549.     /* add the nickname */
  550.     err = PadToColumn(*commentHandle, 28);
  551.     if (err != noErr) goto ErrorExit;
  552.     err = PtrAndHand(nickname + 1, *commentHandle, nickname[0]);
  553.     if (err != noErr) goto ErrorExit;
  554.     
  555.     /* add the comment, splitting among multiple lines if necessary;
  556.      * max line length is 100, so the max size of a section is 100-37 = 63
  557.      */
  558.     err = PadToColumn(*commentHandle, 36);
  559.     if (err != noErr) goto ErrorExit;
  560.     if (comment[0] <= 63)
  561.     {
  562.         err = PtrAndHand(comment + 1, *commentHandle, comment[0]);
  563.         if (err != noErr) goto ErrorExit;
  564.     }
  565.     else
  566.     {
  567.         /* split that sucker */
  568.         StringPtr strStart = comment + 1;
  569.         long strLen = comment[0];
  570.         while (strLen > 0)
  571.         {
  572.             StringPtr strEnd;
  573.             if (strLen > 63)
  574.             {
  575.                 strEnd = strStart + 63;
  576.                 while (strEnd[0] != ' ' && strEnd[0] != '\t' && strEnd > strStart)
  577.                     strEnd--;
  578.                 if (strStart == strEnd)
  579.                 {
  580.                     /* there were no spaces -- split arbitrarily at 63... */
  581.                     strEnd = strStart + 63;
  582.                 }
  583.             }
  584.             else
  585.             {
  586.                 strEnd = strStart + strLen;
  587.             }
  588.             
  589.             /* add this chunk */
  590.             err = PtrAndHand(strStart, *commentHandle, strEnd - strStart);
  591.             if (err != noErr) goto ErrorExit;
  592.             
  593.             /* advance to next chunk */
  594.             strLen -= (strEnd - strStart);
  595.             strStart = strEnd;
  596.             while (strLen > 0 && (strStart[0] == ' ' || strStart[0] == '\t'))
  597.                 strStart++, strLen--;
  598.             
  599.             /* add the end of this line and the start of the next */
  600.             if (strLen > 0)
  601.             {
  602.                 err = PtrAndHand(format->lineEnd + 1, *commentHandle, format->lineEnd[0]);
  603.                 if (err != noErr) goto ErrorExit;
  604.                 c = '\n';
  605.                 err = PtrAndHand(&c, *commentHandle, 1);
  606.                 if (err != noErr) goto ErrorExit;
  607.                 err = PtrAndHand(format->lineStart + 1, *commentHandle, format->lineStart[0]);
  608.                 if (err != noErr) goto ErrorExit;
  609.                 err = PadToColumn(*commentHandle, 36);
  610.                 if (err != noErr) goto ErrorExit;
  611.             }
  612.         }
  613.     }
  614.     
  615.     /* add the end of line */
  616.     err = PtrAndHand(format->lineEnd + 1, *commentHandle, format->lineEnd[0]);
  617.     if (err != noErr) goto ErrorExit;
  618.     c = '\n';
  619.     err = PtrAndHand(&c, *commentHandle, 1);
  620.     if (err != noErr) goto ErrorExit;
  621.     return noErr;
  622.     
  623. ErrorExit:
  624.     if (*commentHandle != NULL)
  625.     {
  626.         DisposeHandle(*commentHandle);
  627.         *commentHandle = NULL;
  628.     }
  629.     return err;
  630. }
  631.  
  632.  
  633. OSErr GetFirstTimeHeader(StringHandle *header, FSSpec *file, CommentFormat *format,
  634.                           StringPtr userName, StringPtr nickname, StringPtr comment, Boolean checkingOut)
  635. {
  636.     StringHandle commentHandle = NULL;
  637.     short revision;
  638.     OSErr err = noErr;
  639.     char c;
  640.  
  641.     /* get the template from the string in our resource file */
  642.     *header = Get1Resource('Comm', 1);
  643.     if (*header == NULL)
  644.     {
  645.         err = resNotFound;
  646.         goto ErrorExit;
  647.     }
  648.     DetachResource(*header);
  649.     
  650.     /* substitute the comment characters */
  651.     err = SubstituteComments(*header, format);
  652.     if (err < noErr) goto ErrorExit;
  653.     
  654.     /* substitute the file name */
  655.     err = ReplaceString(*header, "\p<file name>", file->name);
  656.     if (err < noErr) goto ErrorExit;
  657.     
  658.     /* substitute the user name */
  659.     err = ReplaceString(*header, "\p<user name>", userName);
  660.     if (err < noErr) goto ErrorExit;
  661.     
  662.     /* XXX substitute the copyright */
  663.  
  664.     /* get the revision number from the CKID */
  665.     err = GetRevisionNumber(file, &revision);
  666.     if (err != noErr) goto ErrorExit;
  667.     
  668.     err = BuildComment(&commentHandle, format, revision, checkingOut, comment, nickname);
  669.     if (err != noErr) goto ErrorExit;
  670.     
  671.     /* append the change comment to the header */
  672.     err = HandAndHand(commentHandle, *header);
  673.     DisposeHandle(commentHandle);
  674.     commentHandle = NULL;
  675.     if (err != noErr) goto ErrorExit;
  676.     
  677.     /* append the comment end */
  678.     err = PtrAndHand(format->commentEnd + 1, *header, format->commentEnd[0]);
  679.     if (err != noErr) goto ErrorExit;
  680.     
  681.     /* add another blank line or two */
  682.     c = '\n';
  683.     err = PtrAndHand(&c, *header, 1);
  684.     if (err != noErr) goto ErrorExit;
  685.     err = PtrAndHand(&c, *header, 1);
  686.     if (err != noErr) goto ErrorExit;
  687.  
  688.     return noErr;
  689.     
  690. ErrorExit:
  691.     if (*header != NULL)
  692.     {
  693.         DisposeHandle(*header);
  694.         *header = NULL;
  695.     }
  696.     if (commentHandle != NULL)
  697.         DisposeHandle(commentHandle);
  698.     return err;
  699. }
  700.  
  701.  
  702. OSErr ExtractFileHeader(CommentFormat *format, Handle fileData,
  703.                           StringHandle *header, long *headerEnd)
  704. {
  705.     StringHandle blankHeader = NULL;
  706.     Boolean atEnd;
  707.     Boolean lastLineMatchedCommentEnd;
  708.     OSErr err;
  709.     StringPtr fileLine;
  710.     long fileLength, fileLineLength, headerLength;
  711.     Byte fileState;
  712.     
  713.     /* get blank header from string resource */
  714.     blankHeader = Get1Resource('Comm', 1);
  715.     if (blankHeader == NULL)
  716.     {
  717.         err = resNotFound;
  718.         goto ErrorExit;
  719.     }
  720.     DetachResource(blankHeader);
  721.     
  722.     /* substitute the comment characters */
  723.     err = SubstituteComments(blankHeader, format);
  724.     if (err != noErr) goto ErrorExit;
  725.     
  726.     /* get the header */
  727.     err = GetHeaderFromFile(fileData, format, blankHeader, header);
  728.     if (err != noErr) goto ErrorExit;
  729.     
  730.     /* matched it! loop over lines to the end of comment, adding each
  731.      * line to the handle
  732.      */
  733.     headerLength = GetHandleSize(*header);
  734.     fileState = HGetState(fileData);
  735.     HLock(fileData);
  736.     fileLength = GetHandleSize(fileData) - headerLength;
  737.     fileLine = (*fileData) + headerLength;
  738.     fileLineLength = LineSize(fileLine, fileLength);
  739.     atEnd = lastLineMatchedCommentEnd = false;
  740.     do
  741.     {
  742.         atEnd = lastLineMatchedCommentEnd && fileLineLength == 1 && *fileLine == '\n';
  743.         if (atEnd) break;
  744.         
  745.         lastLineMatchedCommentEnd = MatchLine(fileLine, fileLineLength, format->commentEnd + 1, format->commentEnd[0]);
  746.     
  747.         /* accumulate line into header */
  748.         err = PtrAndHand(fileLine, *header, fileLineLength);
  749.         if (err != noErr) goto ErrorExit;
  750.         
  751.         /* advance the file data pointer */
  752.         fileLine += fileLineLength;
  753.         fileLength -= fileLineLength;
  754.         fileLineLength = LineSize(fileLine, fileLength);
  755.     } while (fileLength > 0);
  756.     
  757.     HSetState(fileData, fileState);
  758.     
  759.     /* return the end-of-header offset */
  760.     *headerEnd = GetHandleSize(*header);
  761.     
  762.     DisposeHandle(blankHeader);
  763.     return noErr;
  764.  
  765. ErrorExit:
  766.     if (blankHeader != NULL)
  767.         DisposeHandle(blankHeader);
  768.     return err;
  769. }
  770.  
  771.  
  772. OSErr ExtractCurrentChangeComment(CommentFormat *format, StringHandle header, StringPtr comment)
  773. {
  774.     OSErr err = noErr;
  775.     StringHandle h = NULL;
  776.     long start, end;
  777.     Byte state;
  778.     StringPtr s;
  779.     long i;
  780.     long stripLength;
  781.     long length;
  782.     Boolean gotPlusSign;
  783.     
  784.     /* get the location of the first change comment */
  785.     err = FindFirstChangeComment(header, format, &start, &end);
  786.     if (err != noErr) return err;
  787.     length = end - start;
  788.     
  789.     /* extract the change comment */
  790.     h = TempNewHandle(end - start, &err);
  791.     if (err != noErr) return err;
  792.     BlockMoveData((*header) + start, *h, length);
  793.     
  794.     /* strip the revision number, initials, and date */
  795.     state = HGetState(h);
  796.     HLock(h);
  797.     gotPlusSign = false;
  798.     for (i = 0, s = *h; i < length && *s != '>'; i++, s++)
  799.         if (*s == '+') gotPlusSign = true;
  800.         /* strip to end of revision number -- right angle bracket */
  801.     if (!gotPlusSign)
  802.     {
  803.         comment[0] = 0;
  804.         DisposeHandle(h);
  805.         return noErr;
  806.     }
  807.     for (s++; i < length && (*s == '\t' || *s == ' '); i++, s++)
  808.         ; /* strip tabs and spaces */
  809.     for ( ; i < length && (*s != '\t') && (*s != ' '); i++, s++)
  810.         ; /* strip non-white-space (date) */
  811.     for ( ; i < length && (*s == '\t' || *s == ' '); i++, s++)
  812.         ; /* strip tabs and spaces */
  813.     for ( ; i < length && (*s != '\t') && (*s != ' '); i++, s++)
  814.         ; /* strip non-white-space (initials) */
  815.     for ( ; i < length && (*s == '\t' || *s == ' '); i++, s++)
  816.         ; /* strip tabs and spaces */
  817.     stripLength = (s - *h);
  818.     HSetState(h, state);
  819.     if (length > stripLength)
  820.     {
  821.         length -= stripLength;
  822.         BlockMoveData(*h + stripLength, *h, length);
  823.         SetHandleSize(h, length);
  824.     }
  825.     else
  826.     {
  827.         SetHandleSize(h, 0);
  828.         length = 0;
  829.     }
  830.     
  831.     /* strip start of line characters */
  832.     if (format->lineStart[0] > 0)
  833.     {
  834.         Str31 theLineStart;
  835.         theLineStart[0] = format->lineStart[0] + 1;
  836.         theLineStart[1] = '\n';
  837.         BlockMoveData(format->lineStart + 1, theLineStart + 2, format->lineStart[0]);
  838.         while (ReplaceString(h, theLineStart, "\p ") > 0)
  839.             ;
  840.     }
  841.     
  842.     /* compress white space */
  843.     ReplaceString(h, "\p\n", "\p ");
  844.     ReplaceString(h, "\p\t", "\p ");
  845.     while (ReplaceString(h, "\p  ", "\p ") > 0)
  846.         ;
  847.     length = GetHandleSize(h);
  848.     
  849.     /* copy to the comment string */
  850.     BlockMoveData(*h, comment + 1, length);
  851.     comment[0] = length;
  852.     DisposeHandle(h);
  853.     return noErr;
  854. }
  855.  
  856.  
  857. OSErr AddChangeComment(FSSpec *file, CommentFormat *format, StringHandle header,
  858.                         StringPtr nickname, StringPtr comment, Boolean checkingOut)
  859. {
  860.     OSErr err = noErr;
  861.     long start, end;
  862.     short revision;
  863.     StringHandle commentHandle = NULL;
  864.     Byte state;
  865.     long newCommentSize;
  866.     
  867.     /* get the location of the first change comment */
  868.     err = FindFirstChangeComment(header, format, &start, &end);
  869.     if (err != noErr) goto ErrorExit;
  870.     
  871.     /* get the revision number from the CKID */
  872.     err = GetRevisionNumber(file, &revision);
  873.     if (err != noErr) goto ErrorExit;
  874.     
  875.     err = BuildComment(&commentHandle, format, revision, checkingOut, comment,
  876.                         nickname);
  877.     if (err != noErr) goto ErrorExit;
  878.  
  879.     /* check the current comment -- is it for this revision with a plus sign?
  880.      * if so, delete it. this is only when checking in...
  881.      */
  882.     state = HGetState(header);
  883.     HLock(header);
  884.     HLock(commentHandle); /* permanent lock -- disposed a little later */
  885.     newCommentSize = GetHandleSize(commentHandle);
  886.     
  887.     if (!checkingOut)
  888.     {
  889.         /* check to see if the current comment is for this revision */
  890.         StringPtr s;
  891.         long i;
  892.         
  893.         for (i = 0, s = (*header) + start; i < end - start && *s != '<'; i++, s++)
  894.             ;
  895.         if (i < end - start && *s == '<')
  896.         {
  897.             Str31 numberString;
  898.             long theNumber;
  899.             
  900.             numberString[0] = 0;
  901.             for (s++, i++; i < end - start && *s >= '0' && *s <= '9'; i++, s++)
  902.                 numberString[++numberString[0]] = *s;
  903.             StringToNum(numberString, &theNumber);
  904.             if (theNumber == revision && *s == '+' && i < end - start)
  905.             {
  906.                 /* delete the current comment */
  907.                 Munger(header, start, NULL, (end - start) + 1, *commentHandle, 0);
  908.             }
  909.         }
  910.     }
  911.     
  912.     /* now add the comment at offset "start" */
  913.     HUnlock(header); /* needs to be unlocked for Munger! */
  914.     Munger(header, start, NULL, 0, *commentHandle, newCommentSize);
  915.     
  916.     /* clean up and go away */
  917.     HSetState(header, state);
  918.     DisposeHandle(commentHandle);
  919.     return noErr;
  920.  
  921. ErrorExit:
  922.     if (commentHandle != NULL)
  923.         DisposeHandle(commentHandle);
  924.     return err;
  925. }
  926.  
  927.  
  928. OSErr AddFirstTimeHeader(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  929. {
  930.     StringHandle header = NULL;
  931.     CommentFormat *format = NULL;
  932.     Handle fileData;
  933.     short refNum;
  934.     OSErr err;
  935.  
  936.     /* is it a text file? if not, nothing to do */
  937.     if (!IsTextFile(file))
  938.         return noErr;
  939.     
  940.     /* does the user confirm that a header should be added? */
  941.     switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  942.     {
  943.     case kConfirmYes:
  944.         break;
  945.     case kConfirmNo:
  946.         return noErr;
  947.     case kConfirmCancel:
  948.         return userCanceledErr;
  949.     }
  950.     
  951.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  952.     
  953.     /* find the comment information for this file type */
  954.     format = GetCommentFormat(file);
  955.     
  956.     /* get new header with first time change comment */
  957.     err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, false);
  958.     if (err != noErr) return RaiseErrorNumber(err);
  959.     
  960.     /* get the original file data */
  961.     err = GetFileData(file, &fileData, &refNum);
  962.     if (err != noErr) return RaiseErrorNumber(err);
  963.     
  964.     /* write the file with its header */
  965.     err = WriteFileWithHeader(refNum, fileData, 0, header);
  966.     FSClose(refNum);
  967.     DisposeHandle(header);
  968.     DisposeHandle(fileData);
  969.     if (err == noErr)
  970.         TaskDone();
  971.     else
  972.         RaiseErrorNumber(err);
  973.     return err;
  974. }
  975.  
  976.  
  977. OSErr AddCheckinComment(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  978. {
  979.     StringHandle header = NULL;
  980.     CommentFormat *format = NULL;
  981.     long headerEnd = 0;
  982.     short refNum = -1;
  983.     Handle fileData = NULL;
  984.     OSErr err;
  985.     
  986.     /* is it a text file? if not, just get an internal comment and get out - no text munging */
  987.     if (!IsTextFile(file))
  988.     {
  989.         if (!GetChangeComment(true, file->name, comment))
  990.             return userCanceledErr;
  991.         else
  992.             return noErr;
  993.     }
  994.  
  995.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  996.     
  997.     /* find the comment information for this file type */
  998.     format = GetCommentFormat(file);
  999.  
  1000.     /* get the original file data */
  1001.     err = GetFileData(file, &fileData, &refNum);
  1002.     if (err != noErr) goto ErrorExit;
  1003.     
  1004.     /* does the file have a header comment? */
  1005.     err = ExtractFileHeader(format, fileData, &header, &headerEnd);
  1006.     if (err != noErr)
  1007.     {
  1008.         /* no header -- does the user confirm adding one? */
  1009.         switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  1010.         {
  1011.         case kConfirmYes:
  1012.             break;
  1013.         case kConfirmNo:
  1014.             FSClose(refNum);
  1015.             TaskDone();
  1016.             return noErr;
  1017.         case kConfirmCancel:
  1018.             err = userCanceledErr;
  1019.             goto ErrorExit;
  1020.         }        
  1021.         
  1022.         /* user confirmed -- get comment from user */
  1023.         if (!GetChangeComment(true, file->name, comment))
  1024.         {
  1025.             err = userCanceledErr;
  1026.             goto ErrorExit;
  1027.         }
  1028.         
  1029.         /* get new header with first time change comment */
  1030.         err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, false);
  1031.         if (err != noErr) goto ErrorExit;
  1032.     }
  1033.     else
  1034.     {
  1035.         /* extract the change comment from the header */
  1036.         err = ExtractCurrentChangeComment(format, header, comment);
  1037.         if (err != noErr) goto ErrorExit;
  1038.         
  1039.         /* get the change comment from the user */
  1040.         if (!GetChangeComment(true, file->name, comment))
  1041.         {
  1042.             err = userCanceledErr;
  1043.             goto ErrorExit;
  1044.         }
  1045.         
  1046.         /* put the change comment into the header */
  1047.         err = AddChangeComment(file, format, header, nickname, comment, false);
  1048.         if (err != noErr) goto ErrorExit;
  1049.     }
  1050.     
  1051.     /* write the file with its header */
  1052.     err = WriteFileWithHeader(refNum, fileData, headerEnd, header);
  1053.     if (err != noErr) goto ErrorExit;
  1054.     FSClose(refNum);
  1055.     DisposeHandle(header);
  1056.     DisposeHandle(fileData);
  1057.     TaskDone();
  1058.     return noErr;
  1059.  
  1060. ErrorExit:
  1061.     if (refNum != -1)
  1062.         FSClose(refNum);
  1063.     if (header != NULL)
  1064.         DisposeHandle(header);
  1065.     if (fileData != NULL)
  1066.         DisposeHandle(fileData);
  1067.     return RaiseErrorNumber(err);
  1068. }
  1069.  
  1070.  
  1071. OSErr AddCheckoutComment(FSSpec *file, StringPtr userName, StringPtr nickname, StringPtr comment)
  1072. {
  1073.     StringHandle header = NULL;
  1074.     CommentFormat *format = NULL;
  1075.     long headerEnd = 0;
  1076.     short refNum = -1;
  1077.     Handle fileData = NULL;
  1078.     OSErr err;
  1079.  
  1080.     /* is it a text file? if not, nothing to do */
  1081.     if (!IsTextFile(file)) return noErr;
  1082.  
  1083.     TaskStart(kProjectDragStrings, kAddingChangeComment, file->name, NULL, NULL, NULL);
  1084.     
  1085.     /* find the comment information for this file type */
  1086.     format = GetCommentFormat(file);
  1087.     
  1088.     /* get the original file data */
  1089.     err = GetFileData(file, &fileData, &refNum);
  1090.     if (err != noErr) goto ErrorExit;
  1091.     
  1092.     /* does the file have a header comment? */
  1093.     err = ExtractFileHeader(format, fileData, &header, &headerEnd);
  1094.     if (err != noErr)
  1095.     {
  1096.         /* no header -- does the user confirm adding one? */
  1097.         switch (ResTextYesNoCancel(kProjectDragStrings, kAddHeader, file->name, NULL, NULL, NULL))
  1098.         {
  1099.         case kConfirmYes:
  1100.             break;
  1101.         case kConfirmNo:
  1102.             FSClose(refNum);
  1103.             TaskDone();
  1104.             return noErr;
  1105.         case kConfirmCancel:
  1106.             err = userCanceledErr;
  1107.             goto ErrorExit;
  1108.         }
  1109.         
  1110.         /* get new header with first time change comment */
  1111.         err = GetFirstTimeHeader(&header, file, format, userName, nickname, comment, true);
  1112.         if (err != noErr) goto ErrorExit;
  1113.     }
  1114.     else
  1115.     {
  1116.         /* has header -- add change comment */
  1117.         err = AddChangeComment(file, format, header, nickname, comment, true);
  1118.         if (err != noErr) goto ErrorExit;
  1119.     }
  1120.     
  1121.     /* write the file with its header */
  1122.     err = WriteFileWithHeader(refNum, fileData, headerEnd, header);
  1123.     if (err != noErr) goto ErrorExit;
  1124.     FSClose(refNum);
  1125.     DisposeHandle(header);
  1126.     DisposeHandle(fileData);
  1127.     TaskDone();
  1128.     return noErr;
  1129.  
  1130. ErrorExit:
  1131.     if (refNum != -1)
  1132.         FSClose(refNum);
  1133.     if (header != NULL)
  1134.         DisposeHandle(header);
  1135.     if (fileData != NULL)
  1136.         DisposeHandle(fileData);
  1137.     return RaiseErrorNumber(err);
  1138. }
  1139.